#====  PACKAGE =================================================================
#         NAME:  Node
#      PURPOSE:  A simple node object for a network.
#   PARAMETERS:  name(String), states(Hash), nodelist(Hash)
#      RETURNS:  True
#===============================================================================

#- Overview --------------------------------------------------------------------
# get_name		get_node		rewind
# get_state_hash	get_timeline		remove_edge
# get_state		set_state		add_node
# get_next		set_name		is_node
# delete_nodelist
#-------------------------------------------------------------------------------

package insj_m_node;
use strict;
use warnings;
use Carp;
@Node::ISA = qw();

#===  FUNCTION  ================================================================
#         NAME:  new
#      PURPOSE:  Constructor
#   PARAMETERS:  name(String)
#      RETURNS:  Node Object
#        USAGE:  my $node = Node->new(name => "1");
#===============================================================================
sub new {

my ($class) = shift @_;
    my (%arg)   = @_;
    my %entry;
    if ( defined $arg{entry} ) {
        %entry = %{ $arg{entry} };
    }
    else {
        %entry = %arg;
    }
    	my %states;
	my %nodelist;
	my %infectionlist;
	my @timeline;
	bless {
		_name          => $entry{name},
		_states        => \%states,
		_nodelist      => \%nodelist,
		_infectionlist => \%infectionlist,
		_timeline      => \@timeline
		}, $class;
}

#---------------------------------------------------------------------------
# getters
#---------------------------------------------------------------------------
sub get_name		     {    $_[0]->{_name} }
sub get_node		     { ${ $_[0]->{_nodelist}}{ $_[1] } }
sub get_infection            { ${ $_[0]->{_infectionlist}}{ $_[1] } }
sub get_state   	     { ${ $_[0]->{_states} }{$_[1]} }
sub get_state_hash	     { %{ $_[0]->{_states} } }
sub get_timeline	     { @{ $_[0]->{_timeline} } }
sub get_number_of_nodes      { scalar keys %{ $_[0]->{_nodelist}} }
sub get_number_of_infections { scalar keys %{ $_[0]->{_infectionslist}} }


#---------------------------------------------------------------------------
# identifier
#---------------------------------------------------------------------------
sub is_node	{ 1 }
sub is_edge	{ exists ${ $_[0]->{_nodelist}}{$_[1]} }

#---------------------------------------------------------------------------
# setters
#---------------------------------------------------------------------------
sub set_name	{ $_[0]->{_name} = $_[1] }

#=====  SETTER  ================================================================
#         NAME:  set_state
#      PURPOSE:  sets the state
#   PARAMETERS:  state (STRING), value (INT), generation (INT)
#      RETURNS:  void
#        USAGE:  $node->set_state("infected", 0, 10);  
#===============================================================================
sub set_state	{

	${ $_[0]->{_states} }{ $_[1]} = $_[2] ; 
	
	my $string = "$_[1];$_[2];$_[3]";
	

	# Clean timeline, in case of same generation change!
	my @line = split(/\;/, $string);
	my %hash;
	my $i = 1;
	foreach (reverse  @{ $_[0]->{_timeline}}) {
		my @line2 = split(/;/);
		
		if ($line[0] eq $line2[0] and $line[2] eq $line2[2]) {
			splice( @{ $_[0]->{_timeline}}, -$i, 1 );
			}
		if ($line[2] > $line2[2]) {last;}
		$i++;
		
		}

	# Add element
	if ($string !~ '_t'){
		push( @{ $_[0]->{_timeline} }, $string);}	

	1;
	}

#---------------------------------------------------------------------------
# functions
#---------------------------------------------------------------------------

#===  FUNCTION  ================================================================
#         NAME:  add_node
#      PURPOSE:  Adds a new node to nodelist, this is an edge to(!) this node.
#   PARAMETERS:  reference to Node object
#      RETURNS:  True
#        USAGE:  $node->add_node($noderef);
#===============================================================================
sub add_node {
	my $self = shift @_;
	my $ref  = $_[0];

	# Test whether $ref is a node object. If not confess (stack trace!).
	unless( eval { $ref->can('is_node') } ) {
      		confess 'Confess: add_node was called without passing a Node object!';
		}

	$self->{_nodelist}{ $ref->get_name() } = $ref;
	1;
	}

#===  FUNCTION  ================================================================
#         NAME:  add_infection
#      PURPOSE:  Adds a new infection to infectionlist.
#   PARAMETERS:  reference to infection object
#      RETURNS:  True
#        USAGE:  $node->add_infection($infectionref);
#===============================================================================
sub add_infection {
	my $self = shift @_;
	my $ref  = $_[0];

	# Test whether $ref is an infection object. If not confess (stack trace!).
	unless( eval { $ref->can('is_infection') } ) {
      		confess 'Confess: add_infection was called without passing an Infection object!';
		}

	$self->{_infectionlist}{ $ref->get_name() } = $ref;
	return $ref;
	}

#===  FUNCTION  ================================================================
#         NAME:  get_next
#      PURPOSE:  iterator for nodelist, if argument 'infection' is given, then 
#                iterator for infectionlist
#   PARAMETERS:  None
#      RETURNS:  Node object, or if the end of nodelist is reached undefined.
#===============================================================================
sub get_next {
	if ( $_[1] ) { if ( $_[1] eq 'infection' ) {
		my $key = each %{ $_[0]->{_infectionlist} };
	        if ( defined $key ) {
	              my $next = $_[0]->{_infectionlist}->{$key};
	              return $next;
	  	      }
		} }
	else {
		my $key = each %{ $_[0]->{_nodelist} };
		if ( defined $key ) {
			my $next = $_[0]->{_nodelist}->{$key};
			return $next;
    			}
		}
	return undef;
	}


#===  FUNCTION  ================================================================
#         NAME:  rewind
#      PURPOSE:  rewind resets the iterator for nodelist
#   PARAMETERS:  None
#      RETURNS:  true
#===============================================================================
sub rewind {
    keys %{ $_[0]->{_nodelist} };
	1;
	}

#===  FUNCTION  ================================================================
#         NAME:  rewind
#      PURPOSE:  rewind resets the iterator for infectionlist
#   PARAMETERS:  None
#      RETURNS:  true
#===============================================================================
sub rewind_infections {
    keys %{ $_[0]->{_infectionlist} };
	1;
	}

#===  FUNCTION  ================================================================
#         NAME:  remove_edge
#      PURPOSE:  Removes an edge (Node reference) from the _nodelist.
#   PARAMETERS:  name(String)
#      RETURNS:  true
#        USAGE:  $node->remove_edge($name);
#===============================================================================
sub remove_edge {
	my $self = shift @_;
	my $name = shift;
	if (defined $self->get_node($name)) {
		delete $self->{_nodelist}{ $name };
		}
	else {
		carp "Carp: Tried to remove the edge \"$name\" from node \"".$self->get_name."\"; edge did not exist!\n";
		}
	1;
	}

#===  FUNCTION  ================================================================
#         NAME:  remove_infection
#      PURPOSE:  Removes an infection (Infection reference) from the _infectionlist.
#   PARAMETERS:  name(String)
#      RETURNS:  true
#        USAGE:  $node->remove_infection($name);
#===============================================================================
sub remove_infection {
	my $self = shift @_;
	my $name = shift;
	if (defined $self->get_infection($name)) {
		delete $self->{_infectionlist}{ $name };
		}
	else {
		carp "Carp: Tried to remove the infection \"$name\" from node \"".$self->get_name."\"; infection did not exist!\n";
		}
	1;
	}

sub delete_nodelist {
	delete $_[0]->{_nodelist};
	}

sub delete_infectionlist {
	delete $_[0]->{_infectionlist};
	}

1;
